#ifdef WX_PRECOMP //
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "sc/config.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include "libxml/xmlmemory.h"
#include "libxml/relaxng.h"
#include "libxml/xpathInternals.h"

#include <sstream>
#include <functional>

#include <boost/foreach.hpp>

#include <wx/string.h>
#include <wx/regex.h>

using namespace sc;

/**
* Validate the value of the attribute
*
* @param sValue
*
* @return true if the value is valid, correponding to the parameters
*/
bool cfg::Attribute::Validate(const wxString& sValue) const
{
    bool bValid = true;

    if (sValue.empty() && m_bOptional)
    {
        // An optional empty value is valid
        return true;
    }

    if (m_type == CHOICE)
    {
        bValid = std::find(m_choices.begin(), m_choices.end(), sValue) != m_choices.end();
    }
    else
    {
        // Typeof verification
        if (m_sType == wxT("int") || m_sType == wxT("integer"))
        {
            bValid = ValidateParams<int>(sValue);
        }
        else if (m_sType == wxT("double"))
        {
            bValid = ValidateParams<double>(sValue);
        }
        else if (m_sType == wxT("float"))
        {
            bValid = ValidateParams<float>(sValue);
        }
        else
        {
            bValid = ValidateStringParams(sValue);
        }
    }
    if (!bValid)
    {
        wxLogMessage(wxT("cfg::Attribute::Validate(%s) of attribute %s => INVALID"), sValue.c_str(), m_sName.c_str());
    }
    return bValid;
}

/**
* Validate the parameters
*
* @param sValue  value to validate
*
* @return true is value is valid
*/
template<class T>
bool cfg::Attribute::ValidateParams(const wxString& sValue) const
{
    bool bValid = true;

    try
    {
        Validator<T> validator( boost::lexical_cast<T>(sValue.utf8_str().data()) );

        // Validation
        BOOST_FOREACH (const TParameters::value_type& rcParam, m_parameters)
        {
            if (rcParam.first == xml::ToWx(xml::rng::_minInclusive))
            {
                bValid &= validator.IsOk(rcParam.second, std::greater_equal<T>());
            }
            else if (rcParam.first == xml::ToWx(xml::rng::_maxInclusive))
            {
                bValid &= validator.IsOk(rcParam.second, std::less_equal<T>());
            }
            else if (rcParam.first == xml::ToWx(xml::rng::_minExclusive))
            {
                bValid &= validator.IsOk(rcParam.second, std::greater<T>());
            }
            else if (rcParam.first == xml::ToWx(xml::rng::_maxExclusive))
            {
                bValid &= validator.IsOk(rcParam.second, std::less<T>());
            }
            else if (rcParam.first == xml::ToWx(xml::rng::_pattern))
            {
                wxRegEx pattern(rcParam.second);
                bValid &= pattern.Matches(sValue);
            }
        }
    }
    catch (boost::bad_lexical_cast& )
    {
        bValid = false;
    }
    return bValid;
}

/**
* Validate the parameters for a string attribute
*
* @param sValue  value to validate
*
* @return true is value is valid
*/
bool cfg::Attribute::ValidateStringParams(const wxString& sValue) const
{
    bool bValid = true;

    // Validation
    BOOST_FOREACH (const TParameters::value_type& rcParam, m_parameters)
    {
        if (rcParam.first == xml::ToWx(xml::rng::_pattern))
        {
            wxRegEx pattern(rcParam.second);
            bValid &= pattern.Matches(sValue);
        }
    }
    return bValid;
}

/**
* Config constructor
*/
Config::Config(void)
: m_rngDoc(NULL)
, m_xpathCtx(NULL)
{
}

/**
* Config destructor
*/
Config::~Config(void)
{
	Clear();
}

/**
* Tells if a file name is a valid Relax NG file
*
* @param sFileRNG
*
* @return true if file is a valid RNG file
*/
bool Config::IsRngValid(const wxString& sFileRNG)
{
    bool bValid = false;

    xmlRelaxNGParserCtxtPtr rngCtx = xmlRelaxNGNewParserCtxt(sFileRNG.utf8_str());
    xmlRelaxNGPtr schema = xmlRelaxNGParse(rngCtx);
    xmlRelaxNGFreeParserCtxt(rngCtx);
    if (schema)
    {
        xmlRelaxNGFree(schema);
        bValid = true;
    }
    return bValid;
}

/**
* Load a config file, ie a RNG file which describ the setup
*
* @param sFile  file name
*
* @return loading error
*/
int Config::LoadRng(const wxString& sFile)
{
    int error = error::NONE;

    // Validate the RNG file
    if (!IsRngValid(sFile))
        return error::SHEMA_INVALID;

    // Load the RNG document
    m_rngDoc   = xmlReadFile(sFile.utf8_str(), NULL, 0);
    m_xpathCtx = xmlXPathNewContext(m_rngDoc);
    if (m_xpathCtx == NULL)
        return error::XPATH_CTX_INVALID;

    xmlXPathRegisterNs(m_xpathCtx, xml::rng::NS, xml::rng::NS_VALUE);

    // Now, we can fill the map of element
    m_elements.clear();

    // Get the root name
    wxString sRoot;
    error = GetRoot(sRoot);
    if (!error)
    {
        if (sRoot.empty())
        {
            error = error::SHEMA_INVALID;
        }
        else
        {
            // insert the root element
            cfg::Element root(sRoot);

            cfg::TElements::value_type val = std::make_pair(sRoot, root);
            std::pair<cfg::TElements::iterator, bool> res = m_elements.insert(val);
            if (res.second)
            {
                // Get the childs of the root element
                error = GetChildren(res.first);
            }
            else
            {
                error = error::UNKNOWN;
            }
        }
    }
    return error;
}

/**
* Get the element root name
*
* @param sRoot [out] root name
*
* @return error
*/
int Config::GetRoot(wxString& sRoot) const
{
    int error = error::NONE;

    if (m_rngDoc == NULL)
        return error::RNG_NOT_LOADED;

    if (m_xpathCtx == NULL)
        return error::XPATH_CTX_INVALID;

    // Search the start
    xmlXPathObjectPtr result = xmlXPathEvalExpression((const xmlChar*)"//sc:start/sc:element", m_xpathCtx);
    if (result == NULL)
        return error::XPATH_INVALID_EXPRESSION;

    if (xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        xmlXPathFreeObject(result);
        return error::XPATH_NO_RESULT;
    }

    // Get the root name
    xmlNodeSetPtr nodeset = result->nodesetval;
    if (nodeset->nodeNr == 1)
    {
        const xmlChar* sName = xmlGetProp(nodeset->nodeTab[0], xml::rng::_name);
        if (sName == NULL)
        {
            error = error::SHEMA_INVALID;
        }
        else
        {
            sRoot = xml::ToWx(sName);
        }
	    xmlXPathFreeObject(result);
    }
    else
    {
        error = error::SHEMA_INVALID;
    }
    return error;
}

/**
* Get the children of an element
*
* @param iterRoot  iterator to the root element of which we are looking for the children
*
* @return error
*/
int Config::GetChildren(cfg::TElements::iterator iterRoot)
{
    int error = error::NONE;

    if (m_rngDoc == NULL)
        return error::RNG_NOT_LOADED;

    if (m_xpathCtx == NULL)
        return error::XPATH_CTX_INVALID;

    // Create the xPath request
    wxString sName =  iterRoot->first;
    xmlChar xPath[200];
    xmlStrPrintf(xPath, 200, (const xmlChar*) "//sc:element[@name='%s']", sName.utf8_str().data());

    // execute it
    xmlXPathObjectPtr result = xmlXPathEvalExpression(xPath, m_xpathCtx);
    if (result == NULL)
        return error::XPATH_INVALID_EXPRESSION;

    if (xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        xmlXPathFreeObject(result);
        return error::XPATH_NO_RESULT;
    }

    // Get the element
    xmlNodeSetPtr nodeset = result->nodesetval;
    if (nodeset->nodeNr == 1)
    {
        // Now we got the element, we can find the children
		error = GetChildren(iterRoot, nodeset->nodeTab[0], cfg::Element::UNIQUE);
		xmlXPathFreeObject(result);
    }
    else
    {
        error = error::SHEMA_INVALID;
    }
    return error;
}

/**
* Get the children of an element
*
* @param iterRoot  iterator on the element
* @param node      xml node of the element
* @param type      type of the element
*
* @return error
*/
int Config::GetChildren(cfg::TElements::iterator iterRoot, xmlNodePtr node, cfg::Element::Type type)
{
	int error = error::NONE;

    xmlNodePtr childNode = node->children;
    while (childNode && !error)
    {
        if (childNode->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(childNode->name, xml::rng::_element))
            {
                // Here is the element
				error = GetElement(iterRoot, childNode, type);
            }
			else if (!xmlStrcmp(childNode->name, xml::rng::_zeroOrMore))
			{
				// It is a multiple (0 or more) element
				error = GetChildren(iterRoot, childNode, cfg::Element::MULTIPLE);
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_optional))
			{
				// It is an optional (0 or 1) element
				error = GetChildren(iterRoot, childNode, cfg::Element::OPTION);
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_attribute))
			{
				// It is an attribute of the parent
				error = GetAttribute(iterRoot->second, childNode, type==cfg::Element::OPTION);
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_ref))
			{
				// It is a reference, jump to it
				xmlNodePtr refNode = GetRefNode(childNode);
				if (refNode)
				{
				    error = GetChildren(iterRoot, refNode, type);
				}
				else
				{
				    error = error::SHEMA_INVALID;
				}
			}
        }
		childNode = childNode->next;
    }
	return error;
}

/**
* Get the Element
*
* @param parent  iterator on the parent of the element
* @param node    xml node of the element
* @param type    type of the element
*
* @return error
*/
int Config::GetElement(cfg::TElements::iterator parent, xmlNodePtr node, cfg::Element::Type type)
{
	int error = error::SHEMA_INVALID;

    xmlChar* sXmlName = xmlGetProp(node, xml::rng::_name);
    if (sXmlName != NULL)
    {
        wxString sName = xml::ToWx(sXmlName);
		cfg::Element elem(sName, type);

		// Insert the child
		cfg::TElements::value_type val = std::make_pair(sName, elem);
		std::pair<cfg::TElements::iterator, bool> res = m_elements.insert(val);
		cfg::TElements::iterator iterElem = res.first;
		if (res.second == false)
		{
			// The element already exist
			iterElem = m_elements.find(sName);
		}

		// Add the child to the parent
		parent->second.AddChild(iterElem);

		// Get the children
		error = GetChildren(iterElem);
	}
	return error;
}

/**
* Get the attribute corresponding to the xml node
*
* @param rElement  Element which corresponde to the current attribute
* @param node      xml node of the attribute
* @param bOptional is Attribute optional
*
* @return error
*/
int Config::GetAttribute(cfg::Element& rElement, xmlNodePtr node, bool bOptional)
{
	int error = error::SHEMA_INVALID;

	xmlChar* sXmlName = xmlGetProp(node, xml::rng::_name);
    if (sXmlName != NULL)
    {
        wxString sName = xml::ToWx(sXmlName);
        cfg::TAttributes::const_iterator iterFound = std::find_if( rElement.GetAttributes().begin()
                                                                 , rElement.GetAttributes().end()
                                                                 , PredFindAttributeByName(sName)
                                                                 );
        if (iterFound == rElement.GetAttributes().end())
        {
            // The attribute doesn't exist allready
            cfg::Attribute attribute(sName, bOptional);

            // Look for the available value(s) for this attribute
            error = GetAttributeValues(attribute, node);

            if (!error)
            {
                rElement.AddAttribute(attribute);
            }
        }
        else
        {
            // The attribute allready exists
            wxLogMessage(wxT("Config::GetAttribute(%s) => %s allready exists."), rElement.GetName().c_str(), sName.c_str());
            error = error::NONE;
            // TODO : verify that this attribute is the same as iterFound
        }
	}
	return error;
}

/**
* Get the attribute values
*
* @param rAttribute  attribute
* @param node        xml node of the attribute's capacity
*
* @return error
*/
int Config::GetAttributeValues(cfg::Attribute& rAttribute, xmlNodePtr node)
{
    int error = error::NONE;

    // Look for the available value(s) for this attribute
    xmlNodePtr childNode = node->children;
    while (childNode && !error)
    {
        if (childNode->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(childNode->name, xml::rng::_data))
            {
                error = GetAttributeData(rAttribute, childNode);
            }
            else if (!xmlStrcmp(childNode->name, xml::rng::_choice))
            {
                error = GetAttributeChoice(rAttribute, childNode);
            }
            else if (!xmlStrcmp(childNode->name, xml::rng::_ref))
            {
                // It is a reference, jump to it
                xmlNodePtr refNode = GetRefNode(childNode);
                if (refNode)
                {
                    error = GetAttributeValues(rAttribute, refNode);
                }
            }
        }
        childNode = childNode->next;
    }
    return error;
}

/**
* Get the data of attribute corresponding to the xml node
*
* @param rAttribute  attribute
* @param node        xml node of the attribute's data
*
* @return error
*/
int Config::GetAttributeData(cfg::Attribute& rAttribute, xmlNodePtr node)
{
	int error = error::SHEMA_INVALID;

	const xmlChar* sType = xmlGetProp(node, xml::rng::_type);
	if (sType)
	{
	    rAttribute.SetType(xml::ToWx(sType));

        // Look for more specifique params
        error = GetAttributeParam(rAttribute, node);
	}
	return error;
}

/**
* Get the choices of attribute corresponding to the xml node
*
* @param rAttribute  attribute
* @param node        xml node of the attribute's data
*
* @return error
*/
int Config::GetAttributeChoice(cfg::Attribute& rAttribute, xmlNodePtr node)
{
	int error = error::NONE;

	rAttribute.SetType( cfg::Attribute::CHOICE );

	xmlNodePtr childNode = node->children;
	while (childNode && !error)
	{
		if (childNode->type == XML_ELEMENT_NODE)
		{
			if (!xmlStrcmp(childNode->name, xml::rng::_value) && childNode->children)
			{
				const xmlChar* sValue = childNode->children->content;
				if (sValue)
				{
                    rAttribute.AddChoice(xml::ToWx(sValue));
				}
			}
		}
		childNode = childNode->next;
	}
	return error;
}

/**
* Get the parameters of the attributes
*
* @param rAttribute
* @param nodeData
*
* @return error
*/
int Config::GetAttributeParam(cfg::Attribute& rAttribute, xmlNodePtr nodeData)
{
    int error = error::NONE;

    xmlNodePtr child = nodeData->children;
    while (child && !error)
    {
        if (child->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(child->name, xml::rng::_param))
            {
                const xmlChar* sType  = xmlGetProp(child, xml::rng::_name);
                const xmlChar* sValue = child->children->content;
                if (sType && sValue)
                {
                    rAttribute.AddParameter(xml::ToWx(sType), xml::ToWx(sValue));
                }
                else
                {
                    error = error::SHEMA_INVALID;
                }
            }
        }
        child = child->next;
    }
    return error;
}

/**
* Remove all information about the Config
*/
void Config::Clear(void)
{
    m_sFilename.clear();
    if (m_rngDoc)
    {
        xmlFreeDoc(m_rngDoc);
        m_rngDoc = NULL;
    }
    if (m_xpathCtx)
    {
        xmlXPathFreeContext(m_xpathCtx);
        m_xpathCtx = NULL;
    }
}

/**
* Get the node corresponding to a reference
*
* @param node node containing the reference
*
* @return xmlNodePtr of the reference, or null if the reference was not found
*/
xmlNodePtr Config::GetRefNode(xmlNodePtr node) const
{
    xmlNodePtr refNode = NULL;

    const xmlChar* sName = xmlGetProp(node, xml::rng::_name);
    if (sName)
    {
        xmlChar xPath[200];
        xmlStrPrintf(xPath, 200, (const xmlChar*) "//sc:define[@name='%s']", sName);
        xmlXPathObjectPtr result = xmlXPathEvalExpression(xPath, m_xpathCtx);
        if (result)
        {
            xmlNodeSetPtr nodeset = result->nodesetval;
            if (nodeset->nodeNr == 1)
            {
                refNode = nodeset->nodeTab[0];
            }
        }
    }
    return refNode;
}
